/*
 * Copyright 2014 Jens K., Jalbum AB
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.package net.jalbum.jprint;
 */
package net.jalbum.areatagging;

import com.adobe.xmp.XMPException;
import com.adobe.xmp.XMPMeta;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Image;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.SystemColor;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.concurrent.ExecutionException;
import javax.imageio.ImageIO;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.InputMap;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPopupMenu;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.SwingWorker;
import jjil.algorithm.RgbAvgGray;
import jjil.core.RgbImage;
import jjil.j2se.RgbImageJ2se;
import se.datadosen.component.Autocompleter;
import se.datadosen.component.CenterLayout;
import se.datadosen.component.ControlPanel;
import se.datadosen.component.JProgressSpinner;
import se.datadosen.component.JSmartDialog;
import se.datadosen.component.RiverLayout;
import se.datadosen.component.WordList;
import se.datadosen.jalbum.AlbumBean;
import se.datadosen.jalbum.AlbumObject;
import se.datadosen.jalbum.Msg;
import se.datadosen.jalbum.OperationAbortedException;
import se.datadosen.jalbum.PluginContext;
import se.datadosen.jalbum.XmpManager;
import se.datadosen.tags.ElementException;
import se.datadosen.util.Debug;
import se.datadosen.util.IO;
import se.datadosen.util.Platform;

/**
 * Main frame for adding, removing and editing area tags to an image.
 */
public class JAreaTaggingDialog extends JSmartDialog {

    private static final KeyStroke cmdBackspaceStroke = KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SPACE, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask());
    private static final KeyStroke deleteStroke = KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0);
    private static final int IMAGE_SIZE = 800;
    private static final Dimension imageBounds = new Dimension(IMAGE_SIZE, IMAGE_SIZE);

    Action deleteAction = new AbstractAction(Msg.getString(this, "ui.menuItemDelete")) {
        {
            if (Platform.isMac()) {
                putValue(Action.ACCELERATOR_KEY, Platform.isMac() ? cmdBackspaceStroke : deleteStroke);
            }
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            removeAreaTag();
        }
    };

    private final PluginContext context;
    private final AlbumObject ao;
    private final Dimension dim;
    private final int originalImageWidth;
    private final int originalImageHeight;

    private final JAreaTaggingSelector display = new JAreaTaggingSelector(this);
    private final JProgressSpinner spinner = new JProgressSpinner(32);
    private final JLabel messages = new JLabel(" ");
    private final JTextField nameField = new JTextField();
    private final JTextField descriptionField = new JTextField();
    private final JTextField typeField = new JTextField();
    private final JPopupMenu popup = new JPopupMenu();
    private final JMenuItem delete = new JMenuItem(deleteAction);
    private final JButton detect = new JButton(Msg.getString(this, "ui.detect"));
    private final ArrayList<AreaTag> areaTags = new ArrayList<AreaTag>();
    private final AreaTagHandler tagHandler = new AreaTagHandler();

    private AreaTag selectedTag;
    private AlbumBean engine;
    private XmpManager manager;
    private XMPMeta meta;

    public boolean proceed = true;

    public JAreaTaggingDialog(PluginContext context, AlbumObject ao) {
        super(context.getJAlbumContext().getFrame(), Msg.getString(new Dummy(), "ui.setAreaTags"), false);
        this.context = context;
        this.ao = ao;

        originalImageWidth = Integer.parseInt(ao.getVars().get("imageWidth").toString());
        originalImageHeight = Integer.parseInt(ao.getVars().get("imageHeight").toString());
        dim = new Dimension(originalImageWidth, originalImageHeight);

        init();
    }

    private void init() {
        manager = ao.getXmpManager();
        meta = ao.getXmpManager().getXMPMeta();
        engine = context.getJAlbumContext().getEngine();
        try {
            engine.init();
        } catch (IOException ex) {
            throw new RuntimeException(ex);
        } catch (OperationAbortedException ex) {
            throw new RuntimeException(ex);
        } catch (ElementException ex) {
            throw new RuntimeException(ex);
        }

        registerActions(okAction, cancelAction);

        // initialize action listener
        initActionListener();

        // initialize popup menu
        popup.add(delete);

        // initialize display (work bench)
        {
            InputMap im = display.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);

            im.put(deleteStroke, "delete");
            if (Platform.isMac()) {
                im.put(cmdBackspaceStroke, "delete");
            }

            display.getActionMap().put("delete", deleteAction);
        }
        display.setLayout(new CenterLayout());
        display.add(spinner);
        display.setBackground(SystemColor.text);
        display.setImageBorder(BorderFactory.createLineBorder(new Color(96, 96, 96)));
        display.setPreferredSize(new Dimension(400, 400));
        display.createToolTip();
        spinner.workStarted();
        new SwingWorker<Image, Object>() {

            @Override
            protected Image doInBackground() throws Exception {
                return ao.loadImage(true, imageBounds);
            }

            @Override
            protected void done() {
                try {
                    spinner.workDone();
                    display.validate();
                    display.setBackgroundImage(get());
                } catch (InterruptedException ex) {
                    throw new RuntimeException(ex); // Should never happen
                } catch (ExecutionException e) {
                    Debug.showErrorDialog(JAreaTaggingDialog.this, e);
                }
            }
        }.execute();
        Autocompleter ac = new Autocompleter(nameField, context.getJAlbumContext().getExplorer().knownKeywords);
        ac.setPostfix("");

        Container content = getContentPane();
        content.setLayout(new BorderLayout());

        content.add(display, BorderLayout.CENTER);

        ControlPanel controls = new ControlPanel();
        controls.setLayout(new RiverLayout());
        controls.add("p center", detect);
        controls.add("left br", new JLabel(Msg.getString(this, "ui.name")));
        controls.add("tab hfill vfill", nameField);
        controls.add("br", new JLabel(Msg.getString(this, "ui.description")));
        controls.add("tab hfill vfill", descriptionField);
        controls.add("br", new JLabel(Msg.getString(this, "ui.type")));
        controls.add("tab hfill vfill", typeField);
        controls.add("br right vfill", messages);

        content.add(controls, BorderLayout.SOUTH);

        // get existing area tags
        try {
            areaTags.addAll(tagHandler.getAreaTags(meta, dim));
        } catch (Exception ex) {
            // area tag information broken, let the user decide what to do...
            askToProceed();
        }

        updateGUI();
        pack();
    }

    public ArrayList<AreaTag> getAreaTags() {
        return areaTags;
    }

    public AreaTag getSelectedTag() {
        return selectedTag;
    }

    public String getNameFieldText() {
        return nameField.getText();
    }

    public String getDescriptionFieldText() {
        return descriptionField.getText();
    }

    private void updateGUI() {
        if (selectedTag != null) {
            // fill text fields with tag information
            nameField.setText(selectedTag.getName());
            descriptionField.setText(selectedTag.getDescription());
            typeField.setText(selectedTag.getType());

            // direct check value in field type (is Face?)
            checkType(typeField.getText());

            // enable components
            nameField.setEnabled(true);
            descriptionField.setEnabled(true);
            typeField.setEnabled(true);
        } else {
            // clean up
            nameField.setText("");
            descriptionField.setText("");
            typeField.setText("");

            // disable components
            nameField.setEnabled(false);
            descriptionField.setEnabled(false);
            typeField.setEnabled(false);
        }
    }

    private void checkType(String type) {
        if (!type.equals("Face")) {
            messages.setText(Msg.getString(this, "ui.typeWarning") + " ");
            messages.setToolTipText(Msg.getString(this, "ui.typeWarningToolTip"));
        } else {
            messages.setText(" ");
        }
    }

    public void setSelectedTag(AreaTag selected) {
        // save changes to previous tag
        if (selectedTag != null) {
            updateTagChanges();
        }

        // change selection
        selectedTag = selected;

        // update work bench and GUI
        display.repaint();
        updateGUI();
    }

    public void addAreaTag(Point p, Dimension dim) {
        float x = 1.0f / dim.width * (float) p.x;
        float y = 1.0f / dim.height * (float) p.y;
        float w = 1.0f / dim.width;
        float h = 1.0f / dim.height;

        // create and add new tag
        AreaTag newTag = new AreaTagRectangle(Msg.getString(this, "ui.newTag"), "", "Face", x, y, w, h);
        areaTags.add(newTag);

        // switch to new added tag
        setSelectedTag(newTag);
    }

    public void removeAreaTag() {
        if (selectedTag != null) {
            areaTags.remove(selectedTag);

            // update selected tag
            display.resetCursor();
            setSelectedTag(null);
        }
    }

    public void showPopup(Point p) {
        popup.show(display, p.x, p.y);
    }

    public void moveFocusToTextfield() {
        nameField.requestFocus();
        nameField.selectAll();
    }

    private void updateTagChanges() {
        if (selectedTag != null) {
            selectedTag.setName(nameField.getText());
            selectedTag.setDescription(descriptionField.getText());
            selectedTag.setType(typeField.getText());
        }
    }

    private void detectFaces() {
        ArrayList<Rectangle> results = new ArrayList<Rectangle>();
        BufferedImage bi;

        InputStream is = null;
        int minScale = 1;
        int maxScale = 60;

        try {
            is = AreaTaggingFaceDetection.class.getResourceAsStream("patterns/HCSB.txt");

            // create face detection object
            AreaTaggingFaceDetection detectHaar = new AreaTaggingFaceDetection(is, minScale, maxScale);

            // convert BufferedImage to JJIL Image
            bi = ImageIO.read(ao.getFile());
            RgbImage im = RgbImageJ2se.toRgbImage(bi);

            // convert image to greyscale 8-bits
            RgbAvgGray toGray = new RgbAvgGray();
            toGray.push(im);

            // initialise face detector with correct pattern and save found Rectangles in results
            results = detectHaar.pushAndReturn(toGray.getFront());

        } catch (jjil.core.Error e) {
            throw new IllegalStateException(e);
        } catch (IOException e) {
            throw new IllegalStateException(e);
        } finally {
            IO.close(is);
        }

        // create new AreaTags for faces found
        for (Rectangle rect : results) {
            float x = 1.0f / bi.getWidth() * (float) (rect.x + rect.width / 2);
            float y = 1.0f / bi.getHeight() * (float) (rect.y + rect.height / 2);
            float w = 1.0f / bi.getWidth() * rect.width;
            float h = 1.0f / bi.getHeight() * rect.height;

            // create new tag
            AreaTagRectangle newTag = new AreaTagRectangle(Msg.getString(this, "ui.newTag"), "", "Face", x, y, w, h);

            // check for duplicates
            boolean isDuplicate = false;

            for (AreaTag tag : areaTags) {
                isDuplicate = tag.equals(newTag);

                if (isDuplicate) {
                    break;
                }
            }

            if (!isDuplicate) {
                areaTags.add(newTag);
            }
        }

        // switch to last found face
        if (!areaTags.isEmpty()) {
            setSelectedTag(areaTags.get(areaTags.size() - 1));
        }
    }

    private void initActionListener() {
        // button detect listener
        detect.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                detectFaces();
            }
        });

        // text field name action listener
        nameField.addKeyListener(new KeyAdapter() {
            @Override
            public void keyTyped(KeyEvent e) {
                display.repaint();
            }
        });

        // hint mouse adapter for text fields
        MouseAdapter showHint = new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                if (!nameField.isEnabled()) {
                    messages.setText(Msg.getString(this, "ui.selectHint") + " ");
                    messages.setToolTipText(Msg.getString(this, "ui.selectHintToolTip"));
                }
            }
        };
        nameField.addMouseListener(showHint);
        descriptionField.addMouseListener(showHint);
        typeField.addMouseListener(showHint);

        // text field type action listener
        typeField.addKeyListener(new KeyAdapter() {
            @Override
            public void keyReleased(KeyEvent e) {
                checkType(typeField.getText());
            }
        });
    }

    private final Action okAction = new AbstractAction(Msg.get("ok")) {
        @Override
        public void actionPerformed(ActionEvent e) {
            // save last changes
            if (selectedTag != null) {
                updateTagChanges();
            }

            // Update word list
            WordList wl = context.getJAlbumContext().getExplorer().knownKeywords;
            for (AreaTag tag : areaTags) {
                String name = tag.getName();
                if (name != null && name.length() > 0) {
                    wl.add(tag.getName());
                }
            }

            // write to file depending on sources
            try {
                if (tagHandler.isSourceMWG() || !tagHandler.isSourceMS()) {
                    tagHandler.saveMWGAreaTags(manager, dim, areaTags);
                }
                if (tagHandler.isSourceMS()) {
                    tagHandler.saveMSAreaTags(manager, dim, areaTags);
                }
                setVisible(false);
            } catch (XMPException ex) {
                Debug.showErrorDialog(JAreaTaggingDialog.this, ex);
            } catch (IOException ex) {
                Debug.showErrorDialog(JAreaTaggingDialog.this, ex);
            }
        }
    };

    private final Action cancelAction = new AbstractAction(Msg.get("cancel")) {
        @Override
        public void actionPerformed(ActionEvent e) {
            setVisible(false);
        }
    };

    private void askToProceed() {
        JButton yesButton = new JButton(Msg.get("ui.yes"));
        JButton noButton = new JButton(Msg.get("ui.no"));

        JOptionPane proceedQuestion = new JOptionPane(Msg.getString(this, "ui.tagBrokenMessage"),
                JOptionPane.WARNING_MESSAGE, JOptionPane.DEFAULT_OPTION, null, new Object[]{noButton, yesButton}, noButton);

        final JDialog proceedDialog = proceedQuestion.createDialog(this, Msg.getString(this, "ui.tagBrokenTitle"));

        yesButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                proceed = true;
                proceedDialog.setVisible(false);
            }
        });

        noButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                proceed = false;
                proceedDialog.setVisible(false);
            }
        });

        proceedDialog.setVisible(true);
    }
}
